package main

import (
	"bytes"
	"fmt"
)

/*
通过复制、拷贝或者叫克隆已有对象的方式来创建新对象的设计模式叫做原型模式，被拷贝的对象也被称作原型对象。
使用原型模式的目的主要是为了节省创建对象所花费的时间和资源消耗，提升性能。
还有一点就是，比如全局配置对象这种也可以当成原型对象，如果不想让程序在运行时修改初始化好的原型对象，
导致影响其他线程的程序执行的时候，也可以用原型模式快速拷贝出一份，再在副本上做运行时自定义修改。
*/
type Node interface {
	String() string

	Parent() Node

	SetParent(node Node)

	Children() []Node

	AddChild(node Node)

	Clone() Node
}

type Element struct {
	text     string
	parent   Node
	children []Node
}

func NewElement(text string) *Element {
	return &Element{
		text,
		nil,
		make([]Node, 0),
	}
}

func (e *Element) String() string {
	buffer := bytes.NewBufferString(e.text)

	for _, c := range e.children {
		text := c.String()
		_, _ = fmt.Fprintf(buffer, "\n %s", text)
	}

	return buffer.String()
}

func (e *Element) Parent() Node {
	return e.parent
}

func (e *Element) SetParent(node Node) {
	e.parent = node
}

func (e *Element) Children() []Node {
	return e.children
}

func (e *Element) AddChild(child Node) {
	copyChild := child.Clone()
	copyChild.SetParent(e)
	e.children = append(e.Children(), child)
}

func (e *Element) Clone() Node {
	cp := &Element{
		text:     e.text,
		parent:   nil,
		children: make([]Node, 0),
	}

	for _, c := range e.children {
		cp.AddChild(c)
	}
	return cp
}

func main() {
	//职级节点 -- 总监
	directorNode := NewElement("Director of Engineering")

	//职级节点 -- 研发经理
	engManagerNode := NewElement("Engineering Manager")
	engManagerNode.AddChild(NewElement("Lead Software Engineer"))
	// 研发经理是总监的下级
	directorNode.AddChild(engManagerNode)

	// 办公室经理也是总监的下级
	officeManagerNode := NewElement("Office Manager")
	directorNode.AddChild(officeManagerNode)

	fmt.Println("")
	fmt.Println("# Company Hierarchy")
	fmt.Print(directorNode)
	fmt.Println("")
	// 从研发经理节点克隆出一颗新的树
	fmt.Println("# Team Hiearachy")
	fmt.Print(engManagerNode.Clone())
}

/*
关于原型模式的总结，我们先来说一下原型模式的优缺点。

原型模式的优点

某些时候克隆比直接new一个对象再逐属性赋值的过程更简洁高效，比如创建层级很深的对象的时候，克隆比直接用构造会方便很多。
可以使用深克隆方式保存对象的状态，可辅助实现撤销操作。

原型模式的缺点

clone方法位于类的内部，当对已有类进行改造的时候，需要修改代码，违背了开闭原则。
当实现深克隆时，需要编写较为复杂的代码，尤其当对象之间存在多重嵌套引用时，为了实现深克隆，每一层对象对应的类都必须支持深克隆。因此，深克隆、浅克隆需要运用得当。
在项目中使用原型模式时，可能需要在项目初始化时就把提供克隆能力的原型对象创建好，在多线程环境下，每个线程处理任务的时候，用到了相关对象，可以去原型对象那里拷贝。不过适合当作原型对象的数据并不多，所以原型模式在开发中的使用频率并不高，如果有机会做项目架构，可以适当考虑，确实需要再在项目中引入这种设计模式。
*/
